//
// Created by ktsq on 2021/8/9.
//

#include "test.h"

#include <kgl.h>

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <stb_image.h>

#include "FPSCamera.h"

int ch3_framebuffer_main() {
    kgl::Log::Init();
    auto win = &kgl::singleton<kgl::Application>::getInstance()->GetWindow();
    glm::ivec2 winSize = win->GetSize();

    // create shader
    auto shader = kgl::MakeRef<kgl::Shader>("../kgl-data/shader/ch1_light.glsl");
    auto framebuffer_shader = kgl::MakeRef<kgl::Shader>("../kgl-data/shader/ch3_framebuffer.glsl");

    // create quad
    float vert_quad[] = {
            // positions   // texCoords
            -1.0f, 1.0f, 0.0f, 1.0f,
            -1.0f, -1.0f, 0.0f, 0.0f,
            1.0f, -1.0f, 1.0f, 0.0f,

            -1.0f, 1.0f, 0.0f, 1.0f,
            1.0f, -1.0f, 1.0f, 0.0f,
            1.0f, 1.0f, 1.0f, 1.0f
    };
    // vao_quad
    unsigned int vao_quad;
    glGenVertexArrays(1, &vao_quad);
    glBindVertexArray(vao_quad);
    // vbo_quad
    unsigned int vbo_quad;
    glGenBuffers(1, &vbo_quad);
    glBindBuffer(GL_ARRAY_BUFFER, vbo_quad);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vert_quad), vert_quad, GL_STATIC_DRAW);
    // pos
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) nullptr);
    glEnableVertexAttribArray(0);
    // texCoords
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *) (2 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // create box
    float vert_box[] = {
            // position          //normal           // texCoords
            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
            0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
            0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
            0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
            -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,

            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
            0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,
            0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
            0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
            -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,

            -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
            -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
            -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
            -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f,

            0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
            0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
            0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
            0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
            0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,

            -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,
            0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f,
            0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
            0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,
            -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f,
            -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f,

            -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,
            0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,
            0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
            0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
            -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
            -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f
    };
    // vao_box
    unsigned int vao_box;
    glGenVertexArrays(1, &vao_box);
    glBindVertexArray(vao_box);
    // vbo_box
    unsigned int vbo_box;
    glGenBuffers(1, &vbo_box);
    glBindBuffer(GL_ARRAY_BUFFER, vbo_box);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vert_box), vert_box, GL_STATIC_DRAW);
    // pos
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) nullptr);
    glEnableVertexAttribArray(0);
    // normal
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) (3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // texCoords
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void *) (6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    // textures and material
    int w, h, ch;
    unsigned int tex_diffuse;
    glGenTextures(1, &tex_diffuse);
    glBindTexture(GL_TEXTURE_2D, tex_diffuse);
    // set texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // load texture
    auto stb_deleter = [](unsigned char *data) { stbi_image_free(data); };
    // diffuse texture
    kgl::Scope<unsigned char, decltype(stb_deleter)> image_diffuse(
            stbi_load("../kgl-data/texture/brickwall.jpg", &w, &h, &ch, 0), stb_deleter);
    if (nullptr != image_diffuse) {
        if (ch == 3)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, image_diffuse.get());
        else if (ch == 4)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_diffuse.get());
        else if (ch == 1)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, image_diffuse.get());
        // gen mipmap
        glGenerateMipmap(GL_TEXTURE_2D);
    } else {
        kgl::Log::ErrorCore("Failed to load diffuse texture");
    }
    shader->Use();
    shader->SetSampler2D("_material.diffuse", 0);
    // specular texture
    unsigned int tex_specular;
    glGenTextures(1, &tex_specular);
    glBindTexture(GL_TEXTURE_2D, tex_specular);
    // set texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    kgl::Scope<unsigned char, decltype(stb_deleter)> image_specular(
            stbi_load("../kgl-data/texture/brickwall_spec.png", &w, &h, &ch, 0), stb_deleter);
    if (nullptr != image_specular) {
        if (ch == 3)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, image_specular.get());
        else if (ch == 4)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_specular.get());
        else if (ch == 1)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, image_specular.get());
        // gen mipmap
        glGenerateMipmap(GL_TEXTURE_2D);
    } else {
        kgl::Log::ErrorCore("Failed to load specular texture");
    }
    shader->SetSampler2D("_material.specular", 1);

    shader->SetInt("_material.shininess", 32);

    // light meta data
    // direct light
    shader->SetInt("_direct_light_nums", 1);
    int index = 0;
    std::string lightStr = "_direct_lights[" + std::to_string(index) + "]";
    shader->SetFloat(lightStr + ".intensity", 1.0);
    shader->SetRGB(lightStr + ".ambient", {0.1f, 0.1f, 0.1f});
    shader->SetRGB(lightStr + ".diffuse", {1.0f, 1.0f, 1.0f});
    shader->SetRGB(lightStr + ".specular", {1.0f, 1.0f, 1.0f});
    glm::vec3 direction(1.0f, 1.0f, 1.0f);
    shader->SetVector3(lightStr + ".direction", direction);

    // transform
    kgl::Transform trans1(glm::vec3(0, 0, -2.0f),
                          glm::vec3(glm::radians(45.0f), 0.0f, 0), glm::vec3(1.0f));

    kgl::Transform trans_quad(glm::vec3(0, 0, -1.0f), glm::vec3(0.0f), glm::vec3(0.8f, 0.6f, 1.0f));

    auto trans_ca = kgl::MakeRef<kgl::Transform>(glm::vec3(0.0f),
                                                 glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(1.0f));

    // frame buffer
    unsigned int framebuffer;
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    // create a color attachment texture
    unsigned int tex_colorBuffer;
    glGenTextures(1, &tex_colorBuffer);
    glBindTexture(GL_TEXTURE_2D, tex_colorBuffer);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, winSize.x, winSize.y, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_colorBuffer, 0);
    framebuffer_shader->SetSampler2D("_framebuffer_tex", 2);
    // create a renderbuffer object for depth and stencil attachment (we won't be sampling these)
    unsigned int rbo;
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, winSize.x,
                          winSize.y); // use a single renderbuffer object for both a depth AND stencil buffer.
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              rbo); // now actually attach it
    // now that we actually created the framebuffer and added all attachments we want to check if it is actually complete now
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
        kgl::Log::Error("FRAMEBUFFER:: Framebuffer is not complete!");
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // script
    auto fpsCamera = new FPSCamera(trans_ca,
                                   kgl::MakeRef<kgl::PerspectiveCamera>(
                                           *trans_ca, glm::radians(60.0f),
                                           (float) winSize.x / (float) winSize.y, 0.01f, 1000.0f));

    ScriptManager scriptManager;
    scriptManager.add_script(fpsCamera);

    glm::vec4 clearColor = {0.24f, 0.24f, 0.24f, 1.0f};
    float rotate_x = 0.0f;

    // render init
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    scriptManager.Setup();

    framebuffer_shader->Use();
    framebuffer_shader->SetMatrix4("_model", trans_quad.get_trans_mat());
    framebuffer_shader->SetMatrix4("_view", fpsCamera->get_camera()->get_view_mat());
    framebuffer_shader->SetMatrix4("_projection", fpsCamera->get_camera()->get_projection_mat());

    while (kgl::singleton<kgl::Application>::getInstance()->isOpen()) {

        scriptManager.Update();

        if (kgl::Input::IsKeyPressed(kgl::Key::Space)) {
            rotate_x += 0.1f;
        }

        trans1.rotation = glm::quat({glm::radians(rotate_x),
                                     glm::radians(1.5f * rotate_x),
                                     glm::radians(0.8f * rotate_x)});

        // render to framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glEnable(GL_DEPTH_TEST); // enable depth testing (is disabled for rendering screen-space quad)

        glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        shader->Use();
        shader->SetMatrix4("_model", trans1.get_trans_mat());
        shader->SetMatrix4("_view", fpsCamera->get_camera()->get_view_mat());
        shader->SetMatrix4("_projection", fpsCamera->get_camera()->get_projection_mat());
        shader->SetVector3("_eye_pos", trans_ca->position);
        shader->SetVector3(lightStr + ".direction", direction);

        glBindVertexArray(vao_box);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex_diffuse);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, tex_specular);

        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        // render to default screen buffer
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glDisable(GL_DEPTH_TEST); // enable depth testing (is disabled for rendering screen-space quad)
        glClearColor(0.3f, 0.5f, 0.5f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        framebuffer_shader->Use();
//        framebuffer_shader->SetMatrix4("_model", trans_quad.get_trans_mat());
//        framebuffer_shader->SetMatrix4("_view", fpsCamera->get_camera()->get_view_mat());
//        framebuffer_shader->SetMatrix4("_projection", fpsCamera->get_camera()->get_projection_mat());


        glBindVertexArray(vao_quad);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, tex_colorBuffer);

        glDrawArrays(GL_TRIANGLES, 0, 6);

        win->Display();
    }

    scriptManager.Destroy();

    glDeleteVertexArrays(1, &vao_box);
    glDeleteVertexArrays(1, &vao_quad);
    glDeleteBuffers(1, &vbo_box);
    glDeleteBuffers(1, &vbo_quad);

    return 0;
}
